/*
 * Rpe port to Java.
 * Copyright (C) 1999  Christopher Edwards
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
package javaforce.codec.gsm;

public class Rpe {

  private short exp_in;
  /* IN   */
  private short mant_in;
  /* IN   */
  private short exp_out;
  /* OUT  */
  private short mant_out;
  /* OUT  */
  private int xMp_point = 0;

  private static final int ENCODE = 0;
  private static final int DECODE = 1;

  private short[] x = new short[40];

  /* signal [0..39]         OUT */

  public void Gsm_RPE_Encoding(
          short[] e, /* -5..-1][0..39][40..44            IN/OUT  */
          short[] xmaxc, /* [0..3] Coded maximum amplitude   OUT     */
          short[] Mc, /* [0..3] RPE grid selection        OUT     */
          int xmaxc_Mc_index, /* Ref. point for xmaxc and Mc            */
          short[] xMc, /* [0..12]                          OUT     */
          int xMc_index /* Ref. for xmc, '+=13' */) {
    short[] xM = new short[13];
    short[] xMp = new short[13];

    Weighting_filter(e);
    /* Sets up the private data member 'x[40]' */
    RPE_grid_selection(xM, Mc, xmaxc_Mc_index);
    /* Sets up xM[13] */

 /* Sets up xmc (13 array locations starting from xMc_index),
		 * xmaxc (one array location at xmaxc_Mc_index),
		 * exp_in and mant_in
     */
    APCM_quantization(xM, xMc, xMc_index, xmaxc, xmaxc_Mc_index);
    /* Sets up xMp[13] */
    APCM_inverse_quantization(xMc, xMp, xMc_index, ENCODE);

    RPE_grid_positioning(Mc[xmaxc_Mc_index], xMp, e, ENCODE);
  }

  /*
	 *  The coefficients of the weighting filter are stored in a table
	 *  (see table 4.4).  The following scaling is used:
	 *
	 *      H[0..10] = integer( real_H[ 0..10] * 8192 );
   */
  private void Weighting_filter(
          short[] e) /* signal [-5..0.39.44] IN  */ {
    int L_result = 0;
    int i = 0;

    /*
		 *  (e[-5..-1] and e[40..44] are allocated by the caller,
		 *  are initially zero and are not written anywhere.)
		 *
		 *  e -= 5;
		 *
		 *  The above case is true with the C code, although java
		 *  does not have pointers so e[0..49] is all set for this
		 *  method.
     */

 /*  Compute the signal x[0..39]
     */
    for (int k = 0; k <= 39; k++) {
      L_result = 8192 >> 1;

      /*  Every one of these multiplications is done twice --
			 *  but I don't see an elegant way to optimize this.
			 *  Do you?
       */

 /* #define STEP( i, H )    (e[ k + i ] * H) */
      i = 0;

      L_result += (e[k + 0] * -134)
              + (e[k + 1] * -374)
              /* + STEP( 2,      0    ) no sense in adding zero */
              + (e[k + 3] * 2054)
              + (e[k + 4] * 5741)
              + (e[k + 5] * 8192)
              + (e[k + 6] * 5741)
              + (e[k + 7] * 2054)
              /* + STEP( 8,      0    ) no sense in adding zero */
              + (e[k + 9] * -374)
              + (e[k + 10] * -134);

      /* 2 adds vs. >>16 => 14, minus one shift to compensate for
			 * those we lost when replacing L_MULT by '*'.
       */
      L_result = Add.SASR(L_result, 13);
      x[k] = (short) ((L_result < Gsm_Def.MIN_WORD ? Gsm_Def.MIN_WORD
              : (L_result > Gsm_Def.MAX_WORD ? Gsm_Def.MAX_WORD
                      : L_result)));
    }
  }

  /*
	 *  The signal x[0..39] is used to select the RPE grid which is
	 *  represented by Mc.
   */
  private void RPE_grid_selection(
          short[] xM, /* [0..12]              OUT */
          short[] Mc_out, /*                      OUT */
          int Mc_index) {
    int m = 0;
    int L_result = 0;
    int EM = 0;
    /* xxx should be L_EM? */
    short Mc = 0;

    int L_common_0_3;

    /* common part of 0 and 3 */
    L_result = 0;

    L_result += STEP(0, 1) + STEP(0, 2) + STEP(0, 3) + STEP(0, 4)
            + STEP(0, 5) + STEP(0, 6) + STEP(0, 7) + STEP(0, 8)
            + STEP(0, 9) + STEP(0, 10) + STEP(0, 11) + STEP(0, 12);

    L_common_0_3 = L_result;

    /* i = 0 */
    L_result += STEP(0, 0);
    L_result <<= 1;
    /* implicit in L_MULT */
    EM = L_result;

    /* i = 1 */
    L_result = 0;
    L_result += STEP(1, 0) + STEP(1, 1) + STEP(1, 2) + STEP(1, 3)
            + STEP(1, 4) + STEP(1, 5) + STEP(1, 6) + STEP(1, 7)
            + STEP(1, 8) + STEP(1, 9) + STEP(1, 10) + STEP(1, 11)
            + STEP(1, 12);

    L_result <<= 1;
    if (L_result > EM) {
      Mc = 1;
      EM = L_result;
    }

    /* i = 2 */
    L_result = 0;
    L_result += STEP(2, 0) + STEP(2, 1) + STEP(2, 2) + STEP(2, 3)
            + STEP(2, 4) + STEP(2, 5) + STEP(2, 6) + STEP(2, 7)
            + STEP(2, 8) + STEP(2, 9) + STEP(2, 10) + STEP(2, 11)
            + STEP(2, 12);

    L_result <<= 1;
    if (L_result > EM) {
      Mc = 2;
      EM = L_result;
    }

    /* i = 3 */
    L_result = L_common_0_3;
    L_result += STEP(3, 12);
    L_result <<= 1;
    if (L_result > EM) {
      Mc = 3;
      EM = L_result;
    }

    /*  Down-sampling by a factor 3 to get the selected xM[0..12]
		 *  RPE sequence.
     */
    for (int i = 0; i <= 12; i++) {
      xM[i] = x[Mc + 3 * i];
    }
    Mc_out[Mc_index] = Mc;
  }

  private int STEP(int m, int i) {
    int L_temp;
    L_temp = Add.SASR(x[m + 3 * i], 2);
    return (L_temp * L_temp);
  }

  private void APCM_quantization(
          short[] xM, /* [0..12]              IN      */
          short[] xMc, /* [0..12]              OUT     */
          int xMc_index,
          short[] xmaxc_out, /*                      OUT     */
          int xmaxc_index)
          throws IllegalArgumentException {

    int itest = 0;
    short xmax = 0, xmaxc = 0, temp = 0, temp1 = 0, temp2 = 0;
    short exp = 0, mant = 0;

    /*  Find the maximum absolute value xmax of xM[0..12].
     */
    for (int i = 0; i <= 12; i++) {
      temp = xM[i];
      temp = Add.GSM_ABS(temp);
      if (temp > xmax) {
        xmax = temp;
      }
    }

    /*  Qantizing and coding of xmax to get xmaxc.
     */
    exp = 0;
    temp = Add.SASR(xmax, 9);
    itest = 0;

    for (int i = 0; i <= 5; i++) {
      if (temp <= 0) {
        itest |= 1;
      } else {
        itest |= 0;
      }
      temp = Add.SASR(temp, 1);

      if (!(exp <= 5)) {
        throw new IllegalArgumentException("APCM_quantization: exp = "
                + exp + " is out of range. Should be <= 5");
      }

      if (itest == 0) {
        exp++;
        /* exp = add (exp, 1) */
      }
    }

    if (!(exp <= 6 && exp >= 0)) {
      throw new IllegalArgumentException("APCM_quantization: exp = "
              + exp + " is out of range. Should be >= -4 and <= 6");
    }

    temp = (short) (exp + 5);

    if (!(temp <= 11 && temp >= 0)) {
      throw new IllegalArgumentException("APCM_quantization: temp = "
              + temp + " is out of range. Should be >= 0 and <= 11");
    }

    xmaxc = Add.GSM_ADD(Add.SASR(xmax, temp), (short) (exp << 3));

    /*   Quantizing and coding of the xM[0..12] RPE sequence
		 *   to get the xMc[0..12]
     */
    APCM_quantization_xmaxc_to_exp_mant(xmaxc, ENCODE);
    exp = exp_in;
    mant = mant_in;

    /*  This computation uses the fact that the decoded version of xmaxc
		 *  can be calculated by using the exponent and the mantissa part of
		 *  xmaxc (logarithmic table).
		 *  So, this method avoids any division and uses only a scaling
		 *  of the RPE samples by a function of the exponent.  A direct
		 *  multiplication by the inverse of the mantissa (NRFAC[0..7]
		 *  found in table 4.5) gives the 3 bit coded version xMc[0..12]
		 *  of the RPE samples.
     */

 /* Direct computation of xMc[0..12] using table 4.5
     */
    if (!(exp <= 4096 && exp >= -4096)) {
      throw new IllegalArgumentException("APCM_quantization: exp = "
              + exp + " is out of range. Should be >= -4096 and <= 4096");
    }
    if (!(mant >= 0 && mant <= 7)) {
      throw new IllegalArgumentException("APCM_quantization: mant = "
              + mant + " is out of range. Should be >= 0 and <= 7");
    }

    temp1 = (short) (6 - exp);
    /* normalization by the exponent */
    temp2 = Gsm_Def.gsm_NRFAC[mant];
    /* inverse mantissa          */

    for (int i = 0; i <= 12; i++) {
      if (!(temp1 >= 0 && temp1 < 16)) {
        throw new IllegalArgumentException("APCM_quantization: temp = "
                + temp + " is out of range. Should be >= 0 and < 16");
      }

      temp = (short) (xM[i] << temp1);
      temp = Add.GSM_MULT(temp, temp2);
      temp = Add.SASR(temp, 12);
      xMc[i + xMc_index] = (short) (temp + 4);
      /* see note below */
    }

    /*  NOTE: This equation is used to make all the xMc[i] positive.
     */
    mant_in = mant;
    exp_in = exp;
    xmaxc_out[xmaxc_index] = xmaxc;
  }

  public void APCM_quantization_xmaxc_to_exp_mant(
          short xmaxc_elem,
          int METHOD_ID)
          throws IllegalArgumentException {
    short exp = 0, mant = 0;
    /* Compute exponent and mantissa of the decoded version of xmaxc
     */

    if (xmaxc_elem > 15) {
      exp = (short) (Add.SASR(xmaxc_elem, 3) - 1);
    }
    mant = (short) (xmaxc_elem - (exp << 3));

    if (mant == 0) {
      exp = (short) -4;
      mant = (short) 7;
    } else {
      while (mant <= 7) {
        mant = (short) (mant << 1 | 1);
        exp--;
      }
      mant -= (short) 8;
    }
    if (exp < -4 || exp > 6) {
      throw new IllegalArgumentException("APCM_quantization_xmaxc_to_exp_mant: exp = "
              + exp + " is out of range. Should be >= -4 and <= 6");
    }
    if (mant < 0 || mant > 7) {
      throw new IllegalArgumentException("APCM_quantization_xmaxc_to_exp_mant: mant = "
              + mant + " is out of range. Should be >= 0 and <= 7");
    }
    if (METHOD_ID == ENCODE) {
      exp_in = exp;
      mant_in = mant;
    } else {
      /* DECODE */
      exp_out = exp;
      mant_out = mant;
    }
  }

  public void Gsm_RPE_Decoding_java(
          short xmaxc_elem, /* From Gsm_Decoder xmaxc short array */
          short Mc_elem, /* From Gsm_Decoder Mc short array */
          int xmc_start, /* Starting point for the three bit part of xmc */
          short[] xmc, /* [0..12], 3 bits     IN      */
          short[] erp /* [0..39]             OUT     */) {
    short xMp[] = new short[13];

    /* exp_out and mant_out are modified in this method */
    APCM_quantization_xmaxc_to_exp_mant(xmaxc_elem, DECODE);

    APCM_inverse_quantization(xmc, xMp, xmc_start, DECODE);

    RPE_grid_positioning(Mc_elem, xMp, erp, DECODE);
  }

  /*
	 *  This part is for decoding the RPE sequence of coded xMc[0..12]
	 *  samples to obtain the xMp[0..12] array.  Table 4.6 is used to get
	 *  the mantissa of xmaxc (FAC[0..7]).
   */
  public void APCM_inverse_quantization(
          short[] xmc, /* [0..12]                      IN      */
          short[] xMp, /* [0..12]                      OUT     */
          int xmc_start,
          int METHOD_ID)
          throws IllegalArgumentException {
    short temp, temp1, temp2, temp3;

    if (METHOD_ID == ENCODE) {
      temp1 = Gsm_Def.gsm_FAC[mant_in];
      temp2 = Add.GSM_SUB((short) 6, exp_in);
    } else {
      /* DECODE */
      temp1 = Gsm_Def.gsm_FAC[mant_out];
      temp2 = Add.GSM_SUB((short) 6, exp_out);
    }
    temp3 = Add.gsm_asl((short) 1, Add.GSM_SUB(temp2, (short) 1));

    xMp_point = 0;

    for (int i = 0; i < 13; i++) {
      /* restore sign   */
      temp = (short) ((xmc[xmc_start++] << 1) - 7);
      if (!(temp <= 7 && temp >= -7)) {
        /* 4 bit signed   */
        throw new IllegalArgumentException("APCM_inverse_quantization: temp = "
                + temp + " is out of range. Should be >= -7 and <= 7");
      }
      temp <<= 12;
      /* 16 bit signed  */
      temp = Add.GSM_MULT_R(temp1, temp);
      temp = Add.GSM_ADD(temp, temp3);
      xMp[xMp_point++] = Add.gsm_asr(temp, temp2);
    }
  }

  /*
	 *  This method computes the reconstructed long term residual signal
	 *  ep[0..39] for the LTP analysis filter.  The inputs are the Mc
	 *  which is the grid position selection and the xMp[0..12] decoded
	 *  RPE samples which are upsampled by a factor of 3 by inserting zero
	 *  values.
   */
  public static void RPE_grid_positioning(
          short Mc, /* grid position        IN      */
          short[] xMp, /* [0..12]              IN      */
          short[] ep, /* [0..39]              OUT     */
          int METHOD_ID)
          throws IllegalArgumentException {
    int i = 13;
    int xMp_index = 0;
    int ep_index;

    if (METHOD_ID == ENCODE) {
      ep_index = 5;
    } else {
      /* Decode */
      ep_index = 0;
    }

    if (!(0 <= Mc && Mc <= 3)) {
      throw new IllegalArgumentException("RPE_grid_positioning: Mc = "
              + Mc + " is out of range. Should be >= 0 and <= 3");
    }

    switch (Mc) {
      case 3:
        ep[ep_index++] = 0;
        do {
          ep[ep_index++] = 0;
          ep[ep_index++] = 0;
          ep[ep_index++] = xMp[xMp_index++];
          --i;
        } while (i != 0);
        break;

      case 2:
        do {
          ep[ep_index++] = 0;
          ep[ep_index++] = 0;
          ep[ep_index++] = xMp[xMp_index++];
          --i;
        } while (i != 0);
        break;

      case 1:
        do {
          ep[ep_index++] = 0;
          ep[ep_index++] = xMp[xMp_index++];
          ep[ep_index++] = 0;
          --i;
        } while (i != 0);
        break;

      case 0:
        do {
          ep[ep_index++] = xMp[xMp_index++];
          ep[ep_index++] = 0;
          ep[ep_index++] = 0;
          --i;
        } while (i != 0);
        break;
    }

    if (METHOD_ID == ENCODE) {
      ep[ep_index++] = 0;
    }
  }
}
